package scaladuct;

import util.GenericAnalysisUtils;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * Created by IntelliJ IDEA.
 * User: zslajchrt
 * Date: 31.3.11
 * Time: 17:25
 * To change this template use File | Settings | File Templates.
 */
public class PhaseMethodRefInfo implements RefInfo {

    private static final long serialVersionUID = 5409742065864507836L;

    private final String name;
    private final Class motherClass;
    private final String motherName;
    private final int motherIndex;
    private final boolean collection;

    private PhaseMethodRefInfo(String name, Class motherClass, String motherMethod, int motherIndex, boolean isCollection) {
        this.name = name;
        this.motherClass = motherClass;
        this.motherName = motherMethod;
        this.motherIndex = motherIndex;
        this.collection = isCollection;
    }

    @Override
    public String name() {
        return this.name;
    }

    @Override
    public boolean collection() {
        return this.collection;
    }

    public Class<?> getRawClass() {
        for (Method m: motherClass.getMethods()) {
            if (m.getName().equals(motherName)) {
                if (collection) {
                    Type argType = m.getGenericParameterTypes()[motherIndex];
                    // If the reference is a collection of references then the reference type is the type
                    // of the first generic parameter of the collection.
                    if (argType instanceof ParameterizedType) {
                        ParameterizedType pt = (ParameterizedType) argType;
                        // in case that maps are supported, the second argument would be taken (Map<String, 'RefType'>)
                        return GenericAnalysisUtils.getRawClassFromType(pt.getActualTypeArguments()[0]);
                    } else {
                        throw new IllegalStateException("Reference is marked as a collection reference, " +
                                "however the type of the collection (" + argType + ") is not parameterized.");
                    }
                } else {
                    return m.getParameterTypes()[motherIndex];
                }
            }
        }
        throw new IllegalStateException("'" + motherName + "' method not found in class '" + motherClass.getName() + "'");
    }

    public Type getType() {
        for (Method m: motherClass.getMethods()) {
            if (m.getName().equals(motherName)) {
                Type argType = m.getGenericParameterTypes()[motherIndex];
                if (collection) {
                    // If the reference is a collection of references then the reference type is the type
                    // of the first generic parameter of the collection.
                    if (argType instanceof ParameterizedType) {
                        ParameterizedType pt = (ParameterizedType) argType;
                        // in case that maps are supported, the second argument would be taken (Map<String, 'RefType'>)
                        return pt.getActualTypeArguments()[0];
                    } else {
                        throw new IllegalStateException("Reference is marked as a collection reference, " +
                                "however the type of the collection (" + argType + ") is not parameterized.");
                    }
                } else {
                    return argType;
                }
            }
        }
        throw new IllegalStateException(motherName + " method not found in class " + motherClass.getName());
    }

    public static PhaseMethodRefInfo newInstance(String name, Class motherClass, String motherMethod, int motherIndex, boolean isCollection) {
        return new PhaseMethodRefInfo(name, motherClass, motherMethod, motherIndex, isCollection);
    }

    public static PhaseMethodRefInfo newInstance(Ref ref) {
        if (ref == null) {
            return null;
        } else {
            return new PhaseMethodRefInfo(ref.name(), ref.motherClass(), ref.motherMethod(), ref.motherArg(), ref.col());
        }
    }

    @Override
    public String toString() {
        return "ref: " + name;
    }

}
